package com.medsoft.drpcp;

import static com.medsoft.drpcp.meta.DrpcpHeadConstant.DRPCP_MSG_HEAD_SIZE;
import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.codec.compression.ZlibCodecFactory;
import io.netty.handler.codec.compression.ZlibWrapper;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;
import io.netty.util.concurrent.DefaultEventExecutorGroup;
import io.netty.util.concurrent.EventExecutorGroup;

import org.dom4j.Document;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.stereotype.Service;

import com.medsoft.drpcp.client.DrpcpClientBusiHandler;
import com.medsoft.drpcp.server.DrpcpServerBusiHandler;
@Service
public class Bootstrapper implements ApplicationContextAware,InitializingBean {
	static final Logger logger = LoggerFactory.getLogger(Bootstrapper.class);
	
	public static ApplicationContext applicationContext;
	
	//private static final String IP = "127.0.0.1";
    
	/**用于分配处理业务线程的线程组个数 */  
    private static int BIZ_GROUP_SIZE = Runtime.getRuntime().availableProcessors(); //默认  
    /** 
     * NioEventLoopGroup实际上就是个线程池, 
     * NioEventLoopGroup在后台启动了n个NioEventLoop来处理Channel事件, 
     * 每一个NioEventLoop负责处理m个Channel, 
     * NioEventLoopGroup从NioEventLoop数组里挨个取出NioEventLoop来处理Channel 
     */  
    private static EventLoopGroup bossGroup; 
    private static EventLoopGroup workerGroup;  
    // 创建一个16个线程的线程组来处理耗时的业务逻辑
    private static EventExecutorGroup busiGroup;
    
	
	@Override
	public void setApplicationContext(ApplicationContext applicationContext)
			throws BeansException {
		Bootstrapper.applicationContext = applicationContext;
		if(applicationContext == null) {
			throw new RuntimeException("applicationContext is null,drpcp deamon initialize failed!");
		}
	}

	@Override
	public void afterPropertiesSet() throws Exception {
		Document drpcpDoc = ConfigParser.load();
		ConfigParser.parse(drpcpDoc);
		createChannels();
		if(GlobalConfig.isServer) {
			run();
		}
		Thread drpcpHeartThread = new Thread(new DrpcpHeart());
		drpcpHeartThread.start();
		register2DrpcpCenter();
		detectNewNode();
	}
	
	private void detectNewNode() {
		if(GlobalConfig.isCloud) {
			Thread detectThread = new Thread(new DrpcpNodeDetect());
			detectThread.start();
		}
	}

	private void register2DrpcpCenter() {
		if(GlobalConfig.isCloud) {
			//TODO 注册到register-center
		}
	}

	private void createChannels() {
		for(Server server : GlobalConfig.getServers().values()) {
			for(Member member : server.getMembers().values()) {
				Channel channel = getChannel(member.getAddress(),member.getPort());
				member.setChannel(channel);
				if(channel == null) {
					member.setConnected(false);
				}
			}
		}
	}

	protected static Channel getChannel(String host, int port) {
	    Channel channel = null;  
        try {  
            channel = getBootstrap().connect(host, port).sync().channel();
            logger.info(String.format("connect to drpcp server (IP[%s],PORT[%s]) success.", host,port));
        } catch (Exception e) {  
            logger.error(String.format("connect to drpcp server (IP[%s],PORT[%s]) failed, please check the drpcp.xml and sure target server is started.", host,port));
            e.printStackTrace(); 
        }  
        return channel;
    }
	
	/** 
     * 初始化Bootstrap 
     * @return 
     */  
    public static Bootstrap getBootstrap(){  
        EventLoopGroup group = new NioEventLoopGroup();  
        Bootstrap b = new Bootstrap();  
        b.group(group).channel(NioSocketChannel.class);  
        b.handler(new ChannelInitializer<Channel>() {  
            @Override  
            protected void initChannel(Channel ch) throws Exception {  
                ChannelPipeline pipeline = ch.pipeline();  
                pipeline.addLast("frameDecoder", new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4));  
                pipeline.addLast("frameEncoder", new LengthFieldPrepender(4));  //最大16M
                //pipeline.addLast(ZlibCodecFactory.newZlibEncoder(ZlibWrapper.GZIP));
                //pipeline.addLast(ZlibCodecFactory.newZlibDecoder(ZlibWrapper.GZIP));
                pipeline.addLast("decoder", new StringDecoder(CharsetUtil.UTF_8));  
                pipeline.addLast("encoder", new StringEncoder(CharsetUtil.UTF_8));  
                pipeline.addLast("drpcpClientBusiHandler", new DrpcpClientBusiHandler());  
            }  
        });  
        b.option(ChannelOption.SO_KEEPALIVE, true);
        b.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000);
        return b;  
    }

	public static void registerBean(String beanName,Class serviceInterface,Class bean) {
		ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) applicationContext;
	    // 获取bean工厂并转换为DefaultListableBeanFactory
	    DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) configurableApplicationContext.getBeanFactory();
	    RootBeanDefinition rbd = new RootBeanDefinition(bean, AbstractBeanDefinition.AUTOWIRE_BY_TYPE, true);
	    MutablePropertyValues propertyValues = new MutablePropertyValues();
	    propertyValues.add("serviceInterface", serviceInterface);
	    rbd.setPropertyValues(propertyValues);
	    beanFactory.registerBeanDefinition(beanName, rbd);
	}

	public static Object getBean(Class serviceInterface) {
		return applicationContext.getBean(serviceInterface);
	}
	
	public void run() throws Exception {
    	busiGroup = new DefaultEventExecutorGroup(GlobalConfig.busiThreadCount);
    	bossGroup = new NioEventLoopGroup();
    	workerGroup = new NioEventLoopGroup(BIZ_GROUP_SIZE);
    	
    	logger.info("preparing to start drpcp server...");
        ServerBootstrap b = new ServerBootstrap();  
        b.group(bossGroup, workerGroup);  
        b.channel(NioServerSocketChannel.class);  
        b.childHandler(new ChannelInitializer<SocketChannel>() {  
        	@Override  
            public void initChannel(SocketChannel ch) throws Exception {
                ChannelPipeline pipeline = ch.pipeline();  
                pipeline.addLast("frameDecoder", new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4));  
                pipeline.addLast("frameEncoder", new LengthFieldPrepender(DRPCP_MSG_HEAD_SIZE)); 
                //pipeline.addLast(ZlibCodecFactory.newZlibEncoder(ZlibWrapper.GZIP));
                //pipeline.addLast(ZlibCodecFactory.newZlibDecoder(ZlibWrapper.GZIP));
                pipeline.addLast("decoder", new StringDecoder(CharsetUtil.UTF_8));  
                pipeline.addLast("encoder", new StringEncoder(CharsetUtil.UTF_8));  
                pipeline.addLast(busiGroup,new DrpcpServerBusiHandler());  
            }  
        }); 
        //监听本地端口，不要指定ip，否则如果一台机器有多个IP时，通过其他IP连接将会被拒绝“由于目标计算机积极拒绝，无法连接。 127.0.0.1:8888”
        b.bind(GlobalConfig.port).sync();  
        logger.info("drpcp server start sucess, listening on port " + GlobalConfig.port + "。");  
    }  
      
    public void shutdown() {  
    	logger.debug("preparing to shutdown drpcp server...");
        workerGroup.shutdownGracefully();  
        bossGroup.shutdownGracefully();
        logger.debug("drpcp server is shutdown.");
    }
}
